package com.nearsoft.academy.sharounding;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PathTest {

    private static final float MAX_REGIONS = 16.0f;
    private static final float REGION_ANGLE_SIZE = 360 / MAX_REGIONS;
    private String name;
    private boolean isActive = false;
    private List<OrientationData> orientationDataList = new ArrayList<OrientationData>();

    public PathTest(String name) {
        this.setName(name);
        this.setActive(true);
    }

    public PathTest() {
        isActive = false;
    }

    public OrientationData addOrientationEvent(float headingAngle, float pitchAngle, float rollAngle) {

        OrientationData orientationData = new OrientationData(headingAngle, pitchAngle, rollAngle);

        orientationDataList.add(orientationData);
        return orientationData;
    }

    public boolean isActive() {
        return isActive;
    }

    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBeginDate() {
        if (this.orientationDataList.size() <= 0) {
            return null;
        } else {
            return this.orientationDataList.get(0).getDateEntered();
        }
    }

    public Date getEndDate() {
        int size = this.orientationDataList.size();
        if (size <= 0) {
            return null;
        } else {
            return this.orientationDataList.get(size - 1).getDateEntered();
        }
    }

    public float getDurationInSeconds() {
        int size = this.orientationDataList.size();
        if (size <= 0) {
            return 0;
        } else {
            return (getEndDate().getTime() - getBeginDate().getTime()) / 1000f;
        }
    }

    public List<OrientationData> getOrientationDataList() {
        return this.orientationDataList;
    }

    @Override
    public String toString() {
        return "PathTest [name=" + name + ", isActive=" + isActive + ", " +
            ", orientationDataList.size =" + orientationDataList.size() + ", Duration = " + getDurationInSecondsString() +
            ", getErrorsRate = " + getErrorsRate() + ", getAvrgRotationSpeed = " + getAvrgRotationSpeed() + "]";
    }

    private String formatTimestamp(Date theDate) {
        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS").format(theDate);
    }

    public String getEndDateString() {
        if (getEndDate() == null) {
            return "[NULL]";
        }
        return formatTimestamp(getEndDate());
    }

    public String getBeginDateString() {
        if (getBeginDate() == null) {
            return "[NULL]";
        }
        return formatTimestamp(getBeginDate());
    }

    public String getDurationInSecondsString() {
        return String.valueOf(getDurationInSeconds());
    }

    public void addOrientationData(OrientationData data) {
        orientationDataList.add(data);
    }

    public float getAvrgRotationSpeed() {
        // If test has just one record, speed is 0
        if (this.orientationDataList.size() <= 1) {
            return 0;
        }

        float sum = 0;
        float vel = 0;
        float dt = 0;
        float dDegree = 0;
        OrientationData lastOrien = null;
        int i = 0;
        for (OrientationData currentOrien : this.getOrientationDataList()) {
            if (lastOrien == null) {
                lastOrien = currentOrien;
            } else {
                dt = currentOrien.getDateEntered().getTime() - lastOrien.getDateEntered().getTime();
                if (dt != 0) {
                    dDegree = Math.abs(currentOrien.getHeadingAngle() - lastOrien.getHeadingAngle());
                    if (dDegree > 180) {
                        dDegree -= 360;
                    }
                    vel = Math.abs(dDegree / (dt));
                    sum += vel;
                    i++;
                }
                lastOrien = currentOrien;
            }
        }
        return (sum / (float) i);
    }

    public static float getAvrgRotationSpeed(List<PathTest> testList) {
        int size = testList.size();
        if (size <= 0) {
            return 0;
        }

        float sum = 0;
        int i = 0;
        for (PathTest currentTest : testList) {
            sum += currentTest.getAvrgRotationSpeed();
            i++;
        }
        return (sum / (float) i);
    }

    public static float getMaxRotationSpeed(List<PathTest> testList) {
        int size = testList.size();
        if (size <= 0) {
            return 0;
        }

        float max = 0;
        for (PathTest currentTest : testList) {
            float speed = currentTest.getAvrgRotationSpeed();
            if (speed > max) {
                max = speed;
            }
        }
        return max;
    }

    public static float getMinRotationSpeed(List<PathTest> testList) {
        int size = testList.size();
        if (size <= 0) {
            return 0;
        }

        float min = 0;
        int i = 0;
        for (PathTest currentTest : testList) {
            float speed = currentTest.getAvrgRotationSpeed();
            if (i <= 0) {
                min = speed;
            } else {
                if (speed < min) {
                    min = speed;
                }
            }
            i++;
        }
        return min;
    }

    public static boolean isValidPosition(float initialAngle, int expectedPosition, float currentAngle) {
        float expectedMax;
        float expectedMin;

        if (currentAngle < initialAngle) {
            currentAngle += 360;
        }

        expectedMin = initialAngle + REGION_ANGLE_SIZE * (float) (expectedPosition - 1);
        expectedMax = initialAngle + REGION_ANGLE_SIZE * (float) (expectedPosition);

        if ((currentAngle < expectedMax) && (currentAngle >= expectedMin)) {
            return true;
        } else {
            return false;
        }
    }

    public float getErrorsRate() {
        if (this.orientationDataList.size() <= 0) {
            return 0;
        }

        List<OrientationData> orienDataList = this.orientationDataList;
        int expectedPosition;
        float initialAngle = orienDataList.get(0).getHeadingAngle();
        float lastAngle = 0;
        int spin = 0;
        int i = 0;
        int errors = 0;
        int spinner = 0;

        // Detect spin direction
        for (OrientationData orienData : orientationDataList) {
            float currentAngle = orienData.getHeadingAngle();
            if (i > 0) {
                if (currentAngle < lastAngle) {
                    spin--;
                }
                if (currentAngle > lastAngle) {
                    spin++;
                }
            }
            lastAngle = currentAngle;
            i++;
        }

        if (spin > 0) {
            expectedPosition = 1;
            spinner = 1;
        } else {
            expectedPosition = 16;
            spinner = -1;
        }

        // Check for errors
        i = 0;
        for (OrientationData orienData : orientationDataList) {
            float currentAngle = orienData.getHeadingAngle();
            if (i <= 0) {
                initialAngle = currentAngle;
            } else {
                // If it's not in the expected position...
                if (!isValidPosition(initialAngle, expectedPosition, currentAngle)) {
                    // ...but the next expected position, then
                    if (isValidPosition(initialAngle, expectedPosition + spinner, currentAngle)) {
                        expectedPosition += spinner; // Expect for the next position
                    } else {
                        errors++;
                    }
                }
            }
            i++;
        }

        
        return errors / (float) i;
    }
}

